C++ 常用设计模式
设计模式
1、工厂模式
在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。工厂模式作为一种创建模式,一般在创建复杂对象时,考虑使用;在创建简单对象时,建议直接new完成一个实例对象的创建。
1.1、简单工厂模式
主要特点是需要在工厂类中做判断,从而创造相应的产品,当增加新产品时,需要修改工厂类。使用简单工厂模式,我们只需要知道具体的产品型号就可以创建一个产品。
缺点:工厂类集中了所有产品类的创建逻辑,如果产品量较大,会使得工厂类变的非常臃肿。
1 /* 2 关键代码:创建过程在工厂类中完成。 3 */ 4 5 #include <iostream> 6 7 using namespace std; 8 9 //定义产品类型信息 10 typedef enum 11 { 12 Tank_Type_56, 13 Tank_Type_96, 14 Tank_Type_Num 15 }Tank_Type; 16 17 //抽象产品类 18 class Tank 19 { 20 public: 21 virtual const string& type() = 0; 22 }; 23 24 //具体的产品类 25 class Tank56 : public Tank 26 { 27 public: 28 Tank56():Tank(),m_strType("Tank56") 29 { 30 } 31 32 const string& type() override 33 { 34 cout << m_strType.data() << endl; 35 return m_strType; 36 } 37 private: 38 string m_strType; 39 }; 40 41 //具体的产品类 42 class Tank96 : public Tank 43 { 44 public: 45 Tank96():Tank(),m_strType("Tank96") 46 { 47 } 48 const string& type() override 49 { 50 cout << m_strType.data() << endl; 51 return m_strType; 52 } 53 54 private: 55 string m_strType; 56 }; 57 58 //工厂类 59 class TankFactory 60 { 61 public: 62 //根据产品信息创建具体的产品类实例,返回一个抽象产品类 63 Tank* createTank(Tank_Type type) 64 { 65 switch(type) 66 { 67 case Tank_Type_56: 68 return new Tank56(); 69 case Tank_Type_96: 70 return new Tank96(); 71 default: 72 return nullptr; 73 } 74 } 75 }; 76 77 78 int main() 79 { 80 TankFactory* factory = new TankFactory(); 81 Tank* tank56 = factory->createTank(Tank_Type_56); 82 tank56->type(); 83 Tank* tank96 = factory->createTank(Tank_Type_96); 84 tank96->type(); 85 86 delete tank96; 87 tank96 = nullptr; 88 delete tank56; 89 tank56 = nullptr; 90 delete factory; 91 factory = nullptr; 92 93 return 0; 94 }
1.2、工厂方法模式
定义一个创建对象的接口,其子类去具体现实这个接口以完成具体的创建工作。如果需要增加新的产品类,只需要扩展一个相应的工厂类即可。
缺点:产品类数据较多时,需要实现大量的工厂类,这无疑增加了代码量。
1 /* 2 关键代码:创建过程在其子类执行。 3 */ 4 5 #include <iostream> 6 7 using namespace std; 8 9 //产品抽象类 10 class Tank 11 { 12 public: 13 virtual const string& type() = 0; 14 }; 15 16 //具体的产品类 17 class Tank56 : public Tank 18 { 19 public: 20 Tank56():Tank(),m_strType("Tank56") 21 { 22 } 23 24 const string& type() override 25 { 26 cout << m_strType.data() << endl; 27 return m_strType; 28 } 29 private: 30 string m_strType; 31 }; 32 33 //具体的产品类 34 class Tank96 : public Tank 35 { 36 public: 37 Tank96():Tank(),m_strType("Tank96") 38 { 39 } 40 const string& type() override 41 { 42 cout << m_strType.data() << endl; 43 return m_strType; 44 } 45 46 private: 47 string m_strType; 48 }; 49 50 //抽象工厂类,提供一个创建接口 51 class TankFactory 52 { 53 public: 54 //提供创建产品实例的接口,返回抽象产品类 55 virtual Tank* createTank() = 0; 56 }; 57 58 //具体的创建工厂类,使用抽象工厂类提供的接口,去创建具体的产品实例 59 class Tank56Factory : public TankFactory 60 { 61 public: 62 Tank* createTank() override 63 { 64 return new Tank56(); 65 } 66 }; 67 68 //具体的创建工厂类,使用抽象工厂类提供的接口,去创建具体的产品实例 69 class Tank96Factory : public TankFactory 70 { 71 public: 72 Tank* createTank() override 73 { 74 return new Tank96(); 75 } 76 }; 77 78 79 int main() 80 { 81 TankFactory* factory56 = new Tank56Factory(); 82 Tank* tank56 = factory56->createTank(); 83 tank56->type(); 84 85 TankFactory* factory96 = new Tank96Factory(); 86 Tank* tank96 = factory96->createTank(); 87 tank96->type(); 88 89 delete tank96; 90 tank96 = nullptr; 91 delete factory96; 92 factory96 = nullptr; 93 94 delete tank56; 95 tank56 = nullptr; 96 delete factory56; 97 factory56 = nullptr; 98 99 return 0; 100 }
1.3、抽象工厂模式
抽象工厂模式提供创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
当存在多个产品系列,而客户端只使用一个系列的产品时,可以考虑使用抽象工厂模式。
缺点:当增加一个新系列的产品时,不仅需要现实具体的产品类,还需要增加一个新的创建接口,扩展相对困难。
1 /* 2 * 关键代码:在一个工厂里聚合多个同类产品。 3 * 以下代码以白色衣服和黑色衣服为例,白色衣服为一个产品系列,黑色衣服为一个产品系列。白色上衣搭配白色裤子, 黑色上衣搭配黑色裤字。每个系列的衣服由一个对应的工厂创建,这样一个工厂创建的衣服能保证衣服为同一个系列。 4 */ 5 6 //抽象上衣类 7 class Coat 8 { 9 public: 10 virtual const string& color() = 0; 11 }; 12 13 //黑色上衣类 14 class BlackCoat : public Coat 15 { 16 public: 17 BlackCoat():Coat(),m_strColor("Black Coat") 18 { 19 } 20 21 const string& color() override 22 { 23 cout << m_strColor.data() << endl; 24 return m_strColor; 25 } 26 private: 27 string m_strColor; 28 }; 29 30 //白色上衣类 31 class WhiteCoat : public Coat 32 { 33 public: 34 WhiteCoat():Coat(),m_strColor("White Coat") 35 { 36 } 37 const string& color() override 38 { 39 cout << m_strColor.data() << endl; 40 return m_strColor; 41 } 42 43 private: 44 string m_strColor; 45 }; 46 47 //抽象裤子类 48 class Pants 49 { 50 public: 51 virtual const string& color() = 0; 52 }; 53 54 //黑色裤子类 55 class BlackPants : public Pants 56 { 57 public: 58 BlackPants():Pants(),m_strColor("Black Pants") 59 { 60 } 61 const string& color() override 62 { 63 cout << m_strColor.data() << endl; 64 return m_strColor; 65 } 66 67 private: 68 string m_strColor; 69 }; 70 71 //白色裤子类 72 class WhitePants : public Pants 73 { 74 public: 75 WhitePants():Pants(),m_strColor("White Pants") 76 { 77 } 78 const string& color() override 79 { 80 cout << m_strColor.data() << endl; 81 return m_strColor; 82 } 83 84 private: 85 string m_strColor; 86 }; 87 88 //抽象工厂类,提供衣服创建接口 89 class Factory 90 { 91 public: 92 //上衣创建接口,返回抽象上衣类 93 virtual Coat* createCoat() = 0; 94 //裤子创建接口,返回抽象裤子类 95 virtual Pants* createPants() = 0; 96 }; 97 98 //创建白色衣服的工厂类,具体实现创建白色上衣和白色裤子的接口 99 class WhiteFactory : public Factory 100 { 101 public: 102 Coat* createCoat() override 103 { 104 return new WhiteCoat(); 105 } 106 107 Pants* createPants() override 108 { 109 return new WhitePants(); 110 } 111 }; 112 113 //创建黑色衣服的工厂类,具体实现创建黑色上衣和白色裤子的接口 114 class BlackFactory : public Factory 115 { 116 Coat* createCoat() override 117 { 118 return new BlackCoat(); 119 } 120 121 Pants* createPants() override 122 { 123 return new BlackPants(); 124 } 125 };
2、策略模式
策略模式是指定义一系列的算法,把它们单独封装起来,并且使它们可以互相替换,使得算法可以独立于使用它的客户端而变化,也是说这些算法所完成的功能类型是一样的,对外接口也是一样的,只是不同的策略为引起环境角色环境角色表现出不同的行为。
相比于使用大量的if...else,使用策略模式可以降低复杂度,使得代码更容易维护。
缺点:可能需要定义大量的策略类,并且这些策略类都要提供给客户端。
2.1、传统的策略模式实现
1 /* 2 * 关键代码:实现同一个接口。 3 * 以下代码实例中,以游戏角色不同的攻击方式为不同的策略,游戏角色即为执行不同策略的环境角色。 4 */ 5 6 #include <iostream> 7 8 using namespace std; 9 10 //抽象策略类,提供一个接口 11 class Hurt 12 { 13 public: 14 virtual void blood() = 0; 15 }; 16 17 //具体的策略实现类,具体实现接口, Adc持续普通攻击 18 class AdcHurt : public Hurt 19 { 20 public: 21 void blood() override 22 { 23 cout << "Adc hurt, Blood loss" << endl; 24 } 25 }; 26 27 //具体的策略实现类,具体实现接口, Apc技能攻击 28 class ApcHurt : public Hurt 29 { 30 public: 31 void blood() override 32 { 33 cout << "Apc Hurt, Blood loss" << endl; 34 } 35 }; 36 37 //环境角色类, 游戏角色战士,传入一个策略类指针参数。 38 class Soldier 39 { 40 public: 41 Soldier(Hurt* hurt):m_pHurt(hurt) 42 { 43 } 44 //在不同的策略下,该游戏角色表现出不同的攻击 45 void attack() 46 { 47 m_pHurt->blood(); 48 } 49 private: 50 Hurt* m_pHurt; 51 }; 52 53 //定义策略标签 54 typedef enum 55 { 56 Hurt_Type_Adc, 57 Hurt_Type_Apc, 58 Hurt_Type_Num 59 }HurtType; 60 61 //环境角色类, 游戏角色法师,传入一个策略标签参数。 62 class Mage 63 { 64 public: 65 Mage(HurtType type) 66 { 67 switch(type) 68 { 69 case Hurt_Type_Adc: 70 m_pHurt = new AdcHurt(); 71 break; 72 case Hurt_Type_Apc: 73 m_pHurt = new ApcHurt(); 74 break; 75 default: 76 break; 77 } 78 } 79 ~Mage() 80 { 81 delete m_pHurt; 82 m_pHurt = nullptr; 83 cout << "~Mage()" << endl; 84 } 85 86 void attack() 87 { 88 m_pHurt->blood(); 89 } 90 private: 91 Hurt* m_pHurt; 92 }; 93 94 //环境角色类, 游戏角色弓箭手,实现模板传递策略。 95 template<typename T> 96 class Archer 97 { 98 public: 99 void attack() 100 { 101 m_hurt.blood(); 102 } 103 private: 104 T m_hurt; 105 }; 106 107 int main() 108 { 109 Archer<ApcHurt>* arc = new Archer<ApcHurt>; 110 arc->attack(); 111 112 delete arc; 113 arc = nullptr; 114 115 return 0; 116 }
2.2、使用函数指针实现策略模式
1 #include <iostream> 2 #include <functional> 3 4 void adcHurt() 5 { 6 std::cout << "Adc Hurt" << std::endl; 7 } 8 9 void apcHurt() 10 { 11 std::cout << "Apc Hurt" << std::endl; 12 } 13 14 //环境角色类, 使用传统的函数指针 15 class Soldier 16 { 17 public: 18 typedef void (*Function)(); 19 Soldier(Function fun): m_fun(fun) 20 { 21 } 22 void attack() 23 { 24 m_fun(); 25 } 26 private: 27 Function m_fun; 28 }; 29 30 //环境角色类, 使用std::function<> 31 class Mage 32 { 33 public: 34 typedef std::function<void()> Function; 35 36 Mage(Function fun): m_fun(fun) 37 { 38 } 39 void attack() 40 { 41 m_fun(); 42 } 43 private: 44 Function m_fun; 45 }; 46 47 int main() 48 { 49 Soldier* soldier = new Soldier(apcHurt); 50 soldier->attack(); 51 delete soldier; 52 soldier = nullptr; 53 return 0; 54 }
3、适配器模式
适配器模式可以将一个类的接口转换成客户端希望的另一个接口,使得原来由于接口不兼容而不能在一起工作的那些类可以在一起工作。通俗的讲就是当我们已经有了一些类,而这些类不能满足新的需求,此时就可以考虑是否能将现有的类适配成可以满足新需求的类。适配器类需要继承或依赖已有的类,实现想要的目标接口。
缺点:过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
3.1、使用复合实现适配器模式
1 /* 2 * 关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。 3 * 以下示例中,假设我们之前有了一个双端队列,新的需求要求使用栈和队列来完成。 4 双端队列可以在头尾删减或增加元素。而栈是一种先进后出的数据结构,添加数据时添加到栈的顶部,删除数据时先删 除栈顶部的数据。因此我们完全可以将一个现有的双端队列适配成一个栈。 5 */ 6 7 //双端队列, 被适配类 8 class Deque 9 { 10 public: 11 void push_back(int x) 12 { 13 cout << "Deque push_back:" << x << endl; 14 } 15 void push_front(int x) 16 { 17 cout << "Deque push_front:" << x << endl; 18 } 19 void pop_back() 20 { 21 cout << "Deque pop_back" << endl; 22 } 23 void pop_front() 24 { 25 cout << "Deque pop_front" << endl; 26 } 27 }; 28 29 //顺序类,抽象目标类 30 class Sequence 31 { 32 public: 33 virtual void push(int x) = 0; 34 virtual void pop() = 0; 35 }; 36 37 //栈,后进先出, 适配类 38 class Stack:public Sequence 39 { 40 public: 41 //将元素添加到堆栈的顶部。 42 void push(int x) override 43 { 44 m_deque.push_front(x); 45 } 46 //从堆栈中删除顶部元素 47 void pop() override 48 { 49 m_deque.pop_front(); 50 } 51 private: 52 Deque m_deque; 53 }; 54 55 //队列,先进先出,适配类 56 class Queue:public Sequence 57 { 58 public: 59 //将元素添加到队列尾部 60 void push(int x) override 61 { 62 m_deque.push_back(x); 63 } 64 //从队列中删除顶部元素 65 void pop() override 66 { 67 m_deque.pop_front(); 68 } 69 private: 70 Deque m_deque; 71 };
3.2、使用继承实现适配器模式
1 //双端队列,被适配类 2 class Deque 3 { 4 public: 5 void push_back(int x) 6 { 7 cout << "Deque push_back:" << x << endl; 8 } 9 void push_front(int x) 10 { 11 cout << "Deque push_front:" << x << endl; 12 } 13 void pop_back() 14 { 15 cout << "Deque pop_back" << endl; 16 } 17 void pop_front() 18 { 19 cout << "Deque pop_front" << endl; 20 } 21 }; 22 23 //顺序类,抽象目标类 24 class Sequence 25 { 26 public: 27 virtual void push(int x) = 0; 28 virtual void pop() = 0; 29 }; 30 31 //栈,后进先出, 适配类 32 class Stack:public Sequence, private Deque 33 { 34 public: 35 void push(int x) 36 { 37 push_front(x); 38 } 39 void pop() 40 { 41 pop_front(); 42 } 43 }; 44 45 //队列,先进先出,适配类 46 class Queue:public Sequence, private Deque 47 { 48 public: 49 void push(int x) 50 { 51 push_back(x); 52 } 53 void pop() 54 { 55 pop_front(); 56 } 57 };
4、单例模式
单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。实现单例模式必须注意一下几点:
-
单例类只能由一个实例化对象。
-
单例类必须自己提供一个实例化对象。
-
单例类必须提供一个可以访问唯一实例化对象的接口。
单例模式分为懒汉和饿汉两种实现方式。
4.1、懒汉单例模式
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化一个对象。在访问量较小,甚至可能不会去访问的情况下,采用懒汉实现,这是以时间换空间。
4.1.1、非线程安全的懒汉单例模式
1 /* 2 * 关键代码:构造函数是私有的,不能通过赋值运算,拷贝构造等方式实例化对象。 3 */ 4 5 //懒汉式一般实现:非线程安全,getInstance返回的实例指针需要delete 6 class Singleton 7 { 8 public: 9 static Singleton* getInstance(); 10 ~Singleton(){} 11 12 private: 13 Singleton(){} //构造函数私有 14 Singleton(const Singleton& obj) = delete; //明确拒绝 15 Singleton& operator=(const Singleton& obj) = delete; //明确拒绝 16 17 static Singleton* m_pSingleton; 18 }; 19 20 Singleton* Singleton::m_pSingleton = NULL; 21 22 Singleton* Singleton::getInstance() 23 { 24 if(m_pSingleton == NULL) 25 { 26 m_pSingleton = new Singleton; 27 } 28 return m_pSingleton; 29 }
4.1.2、线程安全的懒汉单例模式
1 std::mutex mt; 2 3 class Singleton 4 { 5 public: 6 static Singleton* getInstance(); 7 private: 8 Singleton(){} //构造函数私有 9 Singleton(const Singleton&) = delete; //明确拒绝 10 Singleton& operator=(const Singleton&) = delete; //明确拒绝 11 12 static Singleton* m_pSingleton; 13 14 }; 15 Singleton* Singleton::m_pSingleton = NULL; 16 17 Singleton* Singleton::getInstance() 18 { 19 if(m_pSingleton == NULL) 20 { 21 mt.lock(); 22 if(m_pSingleton == NULL) 23 { 24 m_pSingleton = new Singleton(); 25 } 26 mt.unlock(); 27 } 28 return m_pSingleton; 29 }
4.1.3、返回一个reference指向local static对象
这种单例模式实现方式多线程可能存在不确定性:任何一种non-const static对象,不论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦。解决的方法:在程序的单线程启动阶段手工调用所有reference-returning函数。这种实现方式的好处是不需要去delete它。
1 class Singleton 2 { 3 public: 4 static Singleton& getInstance(); 5 private: 6 Singleton(){} 7 Singleton(const Singleton&) = delete; //明确拒绝 8 Singleton& operator=(const Singleton&) = delete; //明确拒绝 9 }; 10 11 12 Singleton& Singleton::getInstance() 13 { 14 static Singleton singleton; 15 return singleton; 16 }
4.2、饿汉单例模式
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
1 //饿汉式:线程安全,注意一定要在合适的地方去delete它 2 class Singleton 3 { 4 public: 5 static Singleton* getInstance(); 6 private: 7 Singleton(){} //构造函数私有 8 Singleton(const Singleton&) = delete; //明确拒绝 9 Singleton& operator=(const Singleton&) = delete; //明确拒绝 10 11 static Singleton* m_pSingleton; 12 }; 13 14 Singleton* Singleton::m_pSingleton = new Singleton(); 15 16 Singleton* Singleton::getInstance() 17 { 18 return m_pSingleton; 19 }
5、原型模式
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。通俗的讲就是当需要创建一个新的实例化对象时,我们刚好有一个实例化对象,但是已经存在的实例化对象又不能直接使用。这种情况下拷贝一个现有的实例化对象来用,可能会更方便。
以下情形可以考虑使用原型模式:
-
当new一个对象,非常繁琐复杂时,可以使用原型模式来进行复制一个对象。比如创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
-
当需要new一个新的对象,这个对象和现有的对象区别不大,我们就可以直接复制一个已有的对象,然后稍加修改。
-
当需要一个对象副本时,比如需要提供对象的数据,同时又需要避免外部对数据对象进行修改,那就拷贝一个对象副本供外部使用。
1 /* 2 * 关键代码:拷贝,return new className(*this); 3 */ 4 #include <iostream> 5 6 using namespace std; 7 8 //提供一个抽象克隆基类。 9 class Clone 10 { 11 public: 12 virtual Clone* clone() = 0; 13 virtual void show() = 0; 14 }; 15 16 //具体的实现类 17 class Sheep:public Clone 18 { 19 public: 20 Sheep(int id, string name):Clone(), 21 m_id(id),m_name(name) 22 { 23 cout << "Sheep() id address:" << &m_id << endl; 24 cout << "Sheep() name address:" << &m_name << endl; 25 } 26 ~Sheep() 27 { 28 } 29 //关键代码拷贝构造函数 30 Sheep(const Sheep& obj) 31 { 32 this->m_id = obj.m_id; 33 this->m_name = obj.m_name; 34 cout << "Sheep(const Sheep& obj) id address:" << &m_id << endl; 35 cout << "Sheep(const Sheep& obj) name address:" << &m_name << endl; 36 } 37 //关键代码克隆函数,返回return new Sheep(*this) 38 Clone* clone() 39 { 40 return new Sheep(*this); 41 } 42 void show() 43 { 44 cout << "id :" << m_id << endl; 45 cout << "name:" << m_name.data() << endl; 46 } 47 private: 48 int m_id; 49 string m_name; 50 }; 51 52 int main() 53 { 54 Clone* s1 = new Sheep(1, "abs"); 55 s1->show(); 56 Clone* s2 = s1->clone(); 57 s2->show(); 58 59 delete s1; 60 s1 = nullptr; 61 delete s2; 62 s2 = nullptr; 63 return 0; 64 }